package com.atguigu.pamentdemo.service.impl;

import com.atguigu.pamentdemo.config.WxPayConfig;
import com.atguigu.pamentdemo.entity.OrderInfo;
import com.atguigu.pamentdemo.entity.RefundInfo;
import com.atguigu.pamentdemo.enums.OrderStatus;
import com.atguigu.pamentdemo.enums.wxpay.WxApiType;
import com.atguigu.pamentdemo.enums.wxpay.WxNotifyType;
import com.atguigu.pamentdemo.enums.wxpay.WxRefundStatus;
import com.atguigu.pamentdemo.enums.wxpay.WxTradeState;
import com.atguigu.pamentdemo.service.WxPayService;
import com.atguigu.pamentdemo.service.orderInfoService;
import com.atguigu.pamentdemo.service.paymentInfoService;
import com.atguigu.pamentdemo.service.refundInfoService;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {

    private final ReentrantLock lock = new ReentrantLock();


    @Resource
    private WxPayConfig wxPayConfig;

    @Resource
    private CloseableHttpClient wxPayClient;

    @Resource
    private orderInfoService orderInfoService;
    @Resource
    private paymentInfoService paymentInfoService;
    @Resource
    private refundInfoService refundInfoService;


    /**
     * 创建订单,调用Native支付接口
     *
     * @param productId
     * @return code_url 和 订单号
     * @throws Exception
     */
    @Override
    public Map<String, Object> nativePay(Long productId) throws Exception {
        log.info("生成订单");

        //生成订单
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);
        String codeUrl = orderInfo.getCodeUrl();
        if (codeUrl != null && !StringUtils.isNotBlank(codeUrl)) {
            log.info("订单已存在,二维码已保存");
            HashMap<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        }


        log.info("调用统一下单API");
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
        //请求body参数
        Gson gson = new Gson();
        HashMap<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        paramsMap.put("description", orderInfo.getTitle());
        paramsMap.put("out_trade_no", orderInfo.getOrderNo());
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));

        HashMap<Object, Object> amountMap = new HashMap<>();
        amountMap.put("total", orderInfo.getTotalFee());
        amountMap.put("currency", "CNY");

        paramsMap.put("amount", amountMap);

        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数: " + jsonParams);

        StringEntity entity = new StringEntity(jsonParams, "UTF-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);


        try {
            //响应体
            String bodyAsString = EntityUtils.toString(response.getEntity());
            //响应状态
            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200) {
                log.info("成功");
            } else {
                log.info("Native下单失败,响应码 = " + statusCode + ",返回结果= " + bodyAsString);
                throw new IOException("request failed");
            }

            //响应结果
            HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            //二维码
            String code_url = resultMap.get("code_url");
            //保存二维码
            String orderNo = orderInfo.getOrderNo();
            orderInfoService.saveCodeUrl(orderNo, code_url);

            HashMap<String, Object> map = new HashMap<>();
            map.put("codeUrl", code_url);
            map.put("orderNo", orderNo);

            return map;
        } finally {
            response.close();
        }
    }

    @Override
    public void processOrder(HashMap<String, Object> bodyMap) {
        log.info("处理订单");
        try {
            //解密报文
            String plainText = decryptFromResource(bodyMap);
            //转换明文
            Gson gson = new Gson();
            HashMap hashMap = gson.fromJson(plainText, HashMap.class);
            String orderNo = (String) hashMap.get("out_trade_no");


            //处理重复的通知
            //保证接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的
            if (lock.tryLock()) {
                //处理重复的通知
                //接口调用的幂等性：无论接口被调用多少次，产生的结果是一致的
                String orderStatus = orderInfoService.getOrderStatus(orderNo);
                if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
                    return;
                }

                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
                //记录支付日志
                paymentInfoService.createPaymentInfo(plainText);
            }


        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        } finally {


            lock.unlock();
        }

    }


    /**
     * 对称解密
     *
     * @param bodyMap
     * @return
     */
    private String decryptFromResource(Map<String, Object> bodyMap) throws
            GeneralSecurityException {
        log.info("密文解密");
        //通知数据
        Map<String, String> resourceMap = (Map) bodyMap.get("resource");
        //数据密文
        String ciphertext = resourceMap.get("ciphertext");
        //随机串
        String nonce = resourceMap.get("nonce");
        //附加数据
        String associatedData = resourceMap.get("associated_data");


        log.info("密文    ===> {}", ciphertext);
        AesUtil aesUtil = new
                AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText =
                aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                        nonce.getBytes(StandardCharsets.UTF_8),
                        ciphertext);
        log.info("明文    ===> {}", plainText);
        return plainText;
    }


    /**
     * 用户取消订单
     *
     * @param orderNo
     */
    @Override
    public void cancelOrder(String orderNo) {
        try {
            closeOrder(orderNo);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //更新商户端的订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
    }


    /**
     * 关单接口的调用
     *
     * @param orderNo
     */
    private void closeOrder(String orderNo) throws Exception {
        log.info("关单接口的调用,订单号 ===>{}", orderNo);

        //创建远程请求对象
        String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
        url = wxPayConfig.getAppid().concat(url);
        HttpPost httpPost = new HttpPost(url);
        Gson gson = new Gson();
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("mchid", wxPayConfig.getMchId());
        String json = gson.toJson(hashMap);
        log.info("请求参数 ===> {}", json);

        //将请求参数设置到请求对象中
        StringEntity entity = new StringEntity(json, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);


        try {
            //响应状态
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功200");
            } else if (statusCode == 204) {
                //处理成功,无返回body
                log.info("成功204");
            } else {
                log.info("Native下单失败,响应吗 = " + statusCode);
                throw new IOException("request failed");
            }
        } finally {
            response.close();
        }
    }

    /**
     * 根据订单号查询微信支付查单接口,核实订单状态
     * 如果订单已支付,则更新商户端订单状态,并记录支付日志
     * 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
     *
     * @param orderNo
     */
    @Override
    public void checkOrderStatus(String orderNo) {
        log.warn("根据订单号核实订单状态 ===>{}", orderNo);

        //调用微信支付查单接口
        String result = null;
        try {
            result = queryOrder(orderNo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Gson gson = new Gson();
        HashMap resultMap = gson.fromJson(result, HashMap.class);

        //获取微信支付端的订单状态
        Object tradeState = resultMap.get("trade_state");

        //判断订单状态
        if (WxTradeState.SUCCESS.getType().equals(tradeState)) {
            log.warn("核实订单已支付 ==>{}", orderNo);

            //如果确认订单已支付则更新本地订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
            //记录支付日志
            paymentInfoService.createPaymentInfo(result);
        }

        if (WxTradeState.NOTPAY.getType().equals(tradeState)) {
            log.warn("核实订单未支付 ==>{}", orderNo);

            //如果订单未支付,则调用关单接口
            try {
                closeOrder(orderNo);
            } catch (Exception e) {

            }
            //更新本地订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
        }

    }

    /**
     * 查单接口调用
     *
     * @param orderNo
     * @return
     * @throws Exception
     */
    @Override
    public String queryOrder(String orderNo) throws Exception {
        log.info("查单接口调用 ===>{}", orderNo);
        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url).concat("?mchid = ").concat(wxPayConfig.getMchId());
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        try {
            //响应体
            String bodyAsString = EntityUtils.toString(response.getEntity());
            //响应状态码
            int statusCode = response.getStatusLine().getStatusCode();
            //处理成功
            if (statusCode == 200) {
                log.info("成功, 返回结果    = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功");
            } else {
                log.info("Native下单失败,响应码    = " + statusCode + ",返回结果    = " +
                        bodyAsString);
                throw new IOException("request failed");
            }
            return bodyAsString;
        } finally {
            response.close();
        }
    }

    /**
     * 退款
     *
     * @param orderNo
     * @param reason
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refund(String orderNo, String reason) {
        log.info("创建退款单记录");
        //根据订单编号创建退款单
        RefundInfo refundsInfo = refundInfoService.createRefundByOrderNo(orderNo, reason);

        log.info("调用退款API");

        //调用统一下单API
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);

        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        //订单编号
        paramsMap.put("out_trade_no", orderNo);
        //退款单编号
        paramsMap.put("out_refund_no", refundsInfo.getRefundNo());
        //退款原因
        paramsMap.put("reason", reason);
        paramsMap.put("notify_url",
                //退款通知地址
                wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));
        Map amountMap = new HashMap();
        //退款金额
        amountMap.put("refund", refundsInfo.getRefund());
        //原订单金额
        amountMap.put("total", refundsInfo.getTotalFee());
        //退款币种
        amountMap.put("currency", "CNY");
        paramsMap.put("amount", amountMap);

        //将参赛转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ==> {}" + jsonParams);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        //将请求报文放入请求对象
        httpPost.setEntity(entity);
        //设置响应报文格式
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求,并完成验签
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpPost);

            //解析响应结果
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功, 退款返回结果    = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("退款异常, 响应码    = " + statusCode + ", 退款返回结果 = " + bodyAsString);
            }
            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,
                    OrderStatus.REFUND_PROCESSING);
            //更新退款单
            refundInfoService.updateRefund(bodyAsString);
        } catch (Exception e) {

        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * https://www.zhihu.com/question/30954626/answer/511646954
     * 查询退款接口调用
     *
     * @param refundNo
     * @return
     */
    @Override
    public String queryRefund(String refundNo) {

        String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
        url = wxPayConfig.getDomain().concat(url);

        //创建远程Get 请求对象
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpGet);

            String bodyToString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功,查询退款返回结果=" + bodyToString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("查询退款异常,响应码 = " + statusCode
                        + "查询退款返回结果 = " + bodyToString);
            }

            return bodyToString;


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 根据退款单号核实退款单状态
     *
     * @param refundNo
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void checkRefundStatus(String refundNo) {
        log.warn("根据退款单号核实退款单状态 ===> {}", refundNo);

        //调用查询退款单接口
        String result = queryRefund(refundNo);

        Gson gson = new Gson();
        HashMap<String, String> hashMap = gson.fromJson(result, HashMap.class);

        //获取微信支付端退款状态
        String status = hashMap.get("status");
        String orderNo = hashMap.get("out_trade_no");
        if (WxRefundStatus.SUCCESS.getType().equals(status)) {
            log.warn("核实订单已退款成功 ===> {}", refundNo);

            //如果确认退款成功,则更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);

            //更新退款单
            refundInfoService.updateRefund(result);
        }

        if (WxRefundStatus.ABNORMAL.getType().equals(status)) {
            log.warn("核实订单退款异常 ===> {}", refundNo);

            //如果确认退款成功,则更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);

            //更新退款单
            refundInfoService.updateRefund(result);
        }
    }

    /**
     * 处理订单和退款单
     *
     * @param bodyMap
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void processRefund(HashMap<String, Object> bodyMap) throws Exception {
        log.error("退款单");

        //解密报文
        String plainText = decryptFromResource(bodyMap);

        //将明文转换成map
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = (String) plainTextMap.get("out_trade_no");

        if (lock.tryLock()) {
            try {
                String orderStatus = orderInfoService.getOrderStatus(orderNo);
                if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {
                    return;
                }

                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);

                //更新退款单
                refundInfoService.updateRefund(plainText);
            } finally {
                lock.unlock();
            }
        }
    }


    /**
     * 申请账单
     *
     * @param billDate
     * @param type
     * @return
     */
    @Override
    public String queryBill(String billDate, String type) {
        log.warn("申请账单接口调用 {}", billDate);
        String url = "";
        if ("tradebill".equals(type)) {
            url = WxApiType.TRADE_BILLS.getType();
        } else if ("fundflowbill".equals(type)) {
            url = WxApiType.FUND_FLOW_BILLS.getType();
        } else {
            throw new RuntimeException("不支持的账单类型");
        }

        url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);

        //创建远程Get 请求对象
        HttpGet httpGet = new HttpGet();
        httpGet.setHeader("Accept", "application/json");

        //使用wxPayClient发送请求得到响应
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpGet);

            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功, 申请账单返回结果    = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("申请账单异常, 响应码    = " + statusCode + ", 申请账单返回结果    = " + bodyAsString);
            }

            //获取账单下载地址
            Gson gson = new Gson();
            HashMap<String, String> hashMap = gson.fromJson(bodyAsString, HashMap.class);
            return hashMap.get("download_url");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    /**
     * 下载账单
     * @param billDate
     * @param type
     * @return
     */
    @Override
    public String downloadBill(String billDate, String type) {
        log.warn("下载账单接口调用{},{}",billDate,type);

        //获取账单url地址
        String downloadUrl = this.queryBill(billDate, type);
        //创建Get请求对象
        HttpGet httpGet = new HttpGet(downloadUrl);
        httpGet.setHeader("Accept","application/json");

        //使用wxPayClient发送请求得到响应
        CloseableHttpResponse response =null;
        try {
             response = wxPayClient.execute(httpGet);

            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode ==200){
                log.info("成功,下载账单返回结果 = "+bodyAsString);
            }else if (statusCode==204){
                log.info("成功");
            }else {
                throw new RuntimeException("下载账单异常,响应码="+statusCode+",下载账单返回结果="+bodyAsString);
            }
            return bodyAsString;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
